home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-9.10-netbook-remix-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / nevow / stan.py < prev    next >
Text File  |  2006-10-06  |  18KB  |  604 lines

  1. # Copyright (c) 2004 Divmod.
  2. # See LICENSE for details.
  3.  
  4. """An s-expression-like syntax for expressing xml in pure python.
  5.  
  6. Stan tags allow you to build XML documents using Python. Stan tags
  7. have special attributes that enable the developer to insert hooks in
  8. the document for locating data and custom rendering.
  9.  
  10. Stan is a DOM, or Document Object Model, implemented using
  11. basic Python types and functions called "flatteners". A flattener is
  12. a function that knows how to turn an object of a specific type
  13. into something that is closer to an HTML string. Stan differs
  14. from the W3C DOM by not being as cumbersome and heavy
  15. weight. Since the object model is built using simple python types
  16. such as lists, strings, and dictionaries, the API is simpler and
  17. constructing a DOM less cumbersome.
  18.  
  19. Stan also makes it convenient to build trees of XML in pure python
  20. code. See nevow.stan.Tag for details, and nevow.tags for tag
  21. prototypes for all of the XHTML element types.
  22. """
  23.  
  24. from __future__ import generators
  25. from zope.interface import implements
  26.  
  27. from nevow import inevow
  28.  
  29.  
  30. class Proto(str):
  31.     """Proto is a string subclass. Instances of Proto, which are constructed
  32.     with a string, will construct Tag instances in response to __call__
  33.     and __getitem__, delegating responsibility to the tag.
  34.     """
  35.     __slots__ = []
  36.  
  37.     def __call__(self, **kw):
  38.         return Tag(self)(**kw)
  39.  
  40.     def __getitem__(self, children):
  41.         return Tag(self)[children]
  42.  
  43.     def fillSlots(self, slotName, slotValue):
  44.         return Tag(self).fillSlots(slotName, slotValue)
  45.  
  46.     def clone(self, deep=True):
  47.         return self
  48.  
  49.  
  50. class xml(object):
  51.     """XML content marker.
  52.  
  53.     xml contains content that is already correct XML and should not be escaped
  54.     to make it XML-safe. xml can contain unicode content and will be encoded to
  55.     utf-8 when flattened.
  56.     """
  57.     __slots__ = ['content']
  58.  
  59.     def __init__(self, content):
  60.         self.content = content
  61.  
  62.     def __repr__(self):
  63.         return '<xml %r>' % self.content
  64.  
  65.  
  66. class raw(str):
  67.     """Raw content marker.
  68.  
  69.     Raw content is never altered in any way. It is a sequence of bytes that will
  70.     be passed through unchanged to the XML output.
  71.  
  72.     You probably don't want this - look at xml first.
  73.     """
  74.     __slots__ = []
  75.  
  76.  
  77. def cdata(data):
  78.     """CDATA section. data must be a string
  79.     """
  80.     return xml('<![CDATA[%s]]>' % data)
  81.  
  82.  
  83. class directive(object):
  84.     """Marker for a directive in a template
  85.     """
  86.     __slots__ = ['name']
  87.     def __init__(self, name):
  88.         self.name = name
  89.  
  90.  
  91.     def __repr__(self):
  92.         return "directive('%s')" % self.name
  93.  
  94.  
  95.     def __hash__(self):
  96.         return hash((directive, self.name))
  97.  
  98.  
  99.     def __cmp__(self, other):
  100.         if isinstance(other, directive):
  101.             return cmp(self.name, other.name)
  102.         return NotImplemented
  103.  
  104.  
  105.  
  106. class slot(object):
  107.     """Marker for slot insertion in a template
  108.     """
  109.     __slots__ = ['name', 'children', 'default']
  110.  
  111.     def __init__(self, name, default=None):
  112.         self.name = name
  113.         self.children = []
  114.         self.default = default
  115.  
  116.     def __repr__(self):
  117.         return "slot('%s')" % self.name
  118.  
  119.     def __getitem__(self, children):
  120.         """Allow slots to have children. These children will not show up in the
  121.         output, but they will be searched for patterns.
  122.         """
  123.         if not isinstance(children, (list, tuple)):
  124.             children = [children]
  125.         self.children.extend(children)
  126.         return self
  127.  
  128.     def __iter__(self):
  129.         """Prevent an infinite loop if someone tries to do
  130.             for x in slot('foo'):
  131.         """
  132.         raise NotImplementedError, "Stan slot instances are not iterable."
  133.  
  134.  
  135.  
  136. class _PrecompiledSlot(object):
  137.     """
  138.     Marker for slot insertion into a template which has been precompiled.
  139.  
  140.     This differs from a normal slot in that it captures some attributes of its
  141.     context at precompilation time so that it can be rendered properly (as
  142.     these attributes are typically lost during precompilation).
  143.     """
  144.     __slots__ = [
  145.         'name', 'children', 'default', 'isAttrib',
  146.         'inURL', 'inJS', 'inJSSingleQuoteString']
  147.  
  148.     def __init__(self, name, children, default, isAttrib, inURL, inJS, inJSSingleQuoteString):
  149.         self.name = name
  150.         self.children = children
  151.         self.default = default
  152.         self.isAttrib = isAttrib
  153.         self.inURL = inURL
  154.         self.inJS = inJS
  155.         self.inJSSingleQuoteString = inJSSingleQuoteString
  156.  
  157.  
  158.     def __repr__(self):
  159.         return (
  160.             '_PrecompiledSlot('
  161.             '%r, isAttrib=%r, inURL=%r, inJS=%r, '
  162.             'inJSSingleQuoteString=%r)') % (
  163.             self.name, self.isAttrib, self.inURL, self.inJS,
  164.             self.inJSSingleQuoteString)
  165.  
  166.  
  167.  
  168. class Tag(object):
  169.     """Tag instances represent XML tags with a tag name, attributes,
  170.     and children. Tag instances can be constructed using the Prototype
  171.     tags in the 'tags' module, or may be constructed directly with a tag
  172.     name. Tags have two special methods, __call__ and __getitem__,
  173.     which make representing trees of XML natural using pure python
  174.     syntax. See the docstrings for these methods for more details.
  175.     """
  176.     implements(inevow.IQ)
  177.  
  178.     specials = ['data', 'render', 'remember', 'pattern', 'key', 'macro']
  179.  
  180.     slotData = None
  181.     def __init__(self, tag, attributes=None, children=None, specials=None):
  182.         self.tagName = tag
  183.         if attributes is None:
  184.             self.attributes = {}
  185.         else:
  186.             self.attributes = attributes
  187.         if children is None:
  188.             self.children = []
  189.         else:
  190.             self.children = children
  191.         if specials is None:
  192.             self._specials = {}
  193.         else:
  194.             self._specials = specials
  195.  
  196.  
  197.     def fillSlots(self, slotName, slotValue):
  198.         """Remember the stan 'slotValue' with the name 'slotName' at this position
  199.         in the DOM. During the rendering of children of this node, slots with
  200.         the name 'slotName' will render themselves as 'slotValue'.
  201.         """
  202.         if self.slotData is None:
  203.             self.slotData = {}
  204.         self.slotData[slotName] = slotValue
  205.         return self
  206.  
  207.     def patternGenerator(self, pattern, default=None):
  208.         """Returns a psudeo-Tag which will generate clones of matching
  209.         pattern tags forever, looping around to the beginning when running
  210.         out of unique matches.
  211.  
  212.         If no matches are found, and default is None, raise an exception,
  213.         otherwise, generate clones of default forever.
  214.  
  215.         You can use the normal stan syntax on the return value.
  216.  
  217.         Useful to find repeating pattern elements. Example rendering function:
  218.  
  219.         >>> def simpleSequence(context, data):
  220.         ...   pattern = context.patternCloner('item')
  221.         ...   return [pattern(data=element) for element in data]
  222.         """
  223.         patterner = _locatePatterns(self, pattern, default)
  224.         return PatternTag(patterner)
  225.  
  226.     def allPatterns(self, pattern):
  227.         """Return a list of all matching pattern tags, cloned.
  228.  
  229.         Useful if you just want to insert them in the output in one
  230.         place.
  231.  
  232.         E.g. the sequence renderer's header and footer are found with this.
  233.         """
  234.         return [tag.clone(deep=False, clearPattern=True) for tag in
  235.                 specialMatches(self, 'pattern', pattern)]
  236.  
  237.     def onePattern(self, pattern):
  238.         """
  239.         Return a single matching pattern, cloned.
  240.  
  241.         If there is more than one matching pattern or no matching patterns,
  242.         raise an exception.
  243.  
  244.         Useful in the case where you want to locate one and only one sub-tag
  245.         and do something with it.
  246.         """
  247.         data = getattr(self, 'pattern', Unset)
  248.         if data == pattern:
  249.             result = self
  250.         else:
  251.             result = _locateOne(
  252.                 pattern,
  253.                 lambda pattern: specialMatches(self, 'pattern', pattern),
  254.                 'pattern')
  255.         return result.clone(deep=False, clearPattern=True)
  256.  
  257.  
  258.     def __call__(self, **kw):
  259.         """Change attributes of this tag. This is implemented using
  260.         __call__ because it then allows the natural syntax::
  261.  
  262.           table(width="100%", height="50%", border="1")
  263.  
  264.         Attributes may be 'invisible' tag instances (so that
  265.         C{a(href=invisible(data="foo", render=myhrefrenderer))} works),
  266.         strings, functions, or any other object which has a registered
  267.         flattener.
  268.  
  269.         If the attribute is a python keyword, such as 'class', you can
  270.         add an underscore to the name, like 'class_'.
  271.  
  272.         A few magic attributes have values other than these, as they
  273.         are not serialized for output but rather have special purposes
  274.         of their own:
  275.  
  276.          - data: The value is saved on the context stack and passed to
  277.            render functions.
  278.  
  279.          - render: A function to call that may modify the tag in any
  280.            way desired.
  281.  
  282.          - remember: Remember the value on the context stack with
  283.            context.remember(value) for later lookup with
  284.            context.locate()
  285.  
  286.          - pattern: Value should be a key that can later be used to
  287.            locate this tag with context.patternGenerator() or
  288.            context.allPatterns()
  289.  
  290.          - key: A string used to give the node a unique label.  This
  291.            is automatically namespaced, so in C{span(key="foo")[span(key="bar")]}
  292.            the inner span actually has a key of 'foo.bar'.  The key is
  293.            intended for use as e.g. an html 'id' attribute, but will
  294.            is not automatically output.
  295.  
  296.          - macro - A function which will be called once in the lifetime
  297.            of the template, when the template is loaded. The return
  298.            result from this function will replace this Tag in the template.
  299.         """
  300.         if not kw:
  301.             return self
  302.  
  303.         for name in self.specials:
  304.             if kw.has_key(name):
  305.                 setattr(self, name, kw[name])
  306.                 del kw[name]
  307.  
  308.         for k, v in kw.iteritems():
  309.             if k[-1] == '_':
  310.                 k = k[:-1]
  311.             elif k[0] == '_':
  312.                 k = k[1:]
  313.             self.attributes[k] = v
  314.         return self
  315.  
  316.     def __getitem__(self, children):
  317.         """Add children to this tag. Multiple children may be added by
  318.         passing a tuple or a list. Children may be other tag instances,
  319.         strings, functions, or any other object which has a registered
  320.         flatten.
  321.  
  322.         This is implemented using __getitem__ because it then allows
  323.         the natural syntax::
  324.  
  325.           html[
  326.               head[
  327.                   title["Hello World!"]
  328.               ],
  329.               body[
  330.                   "This is a page",
  331.                   h3["How are you!"],
  332.                   div(style="color: blue")["I hope you are fine."]
  333.               ]
  334.           ]
  335.         """
  336.         if not isinstance(children, (list, tuple)):
  337.             children = [children]
  338.         self.children.extend(children)
  339.         return self
  340.  
  341.     def __iter__(self):
  342.         """Prevent an infinite loop if someone tries to do
  343.             for x in stantaginstance:
  344.         """
  345.         raise NotImplementedError, "Stan tag instances are not iterable."
  346.  
  347.     def _clearSpecials(self):
  348.         """Clears all the specials in this tag. For use by flatstan.
  349.         """
  350.         self._specials = {}
  351.  
  352.     # FIXME: make this function actually be used.
  353.     def precompilable(self):
  354.         """Is this tag precompilable?
  355.  
  356.         Tags are precompilable if they will not be modified by a user
  357.         render function.
  358.  
  359.         Currently, the following attributes prevent the tag from being
  360.         precompiled:
  361.  
  362.          - render (because the function can modify its own tag)
  363.          - pattern (because it is locatable and thus modifiable by an
  364.                     enclosing renderer)
  365.         """
  366.         return self.render is Unset and self.pattern is Unset
  367.  
  368.     def _clone(self, obj, deep):
  369.         if hasattr(obj, 'clone'):
  370.             return obj.clone(deep)
  371.         elif isinstance(obj, (list, tuple)):
  372.             return [self._clone(x, deep)
  373.                     for x in obj]
  374.         else:
  375.             return obj
  376.  
  377.     def clone(self, deep=True, clearPattern=False):
  378.         """Return a clone of this tag. If deep is True, clone all of this
  379.         tag's children. Otherwise, just shallow copy the children list
  380.         without copying the children themselves.
  381.         """
  382.         if deep:
  383.             newchildren = [self._clone(x, True) for x in self.children]
  384.         else:
  385.             newchildren = self.children[:]
  386.         newattrs = self.attributes.copy()
  387.         for key in newattrs:
  388.             newattrs[key]=self._clone(newattrs[key], True)
  389.  
  390.         newslotdata = None
  391.         if self.slotData:
  392.             newslotdata = self.slotData.copy()
  393.             for key in newslotdata:
  394.                 newslotdata[key] = self._clone(newslotdata[key], True)
  395.  
  396.         newtag = Tag(
  397.             self.tagName,
  398.             attributes=newattrs,
  399.             children=newchildren,
  400.             specials=self._specials.copy()
  401.             )
  402.         newtag.slotData = newslotdata
  403.         if clearPattern:
  404.             newtag.pattern = None
  405.  
  406.         return newtag
  407.  
  408.     def clear(self):
  409.         """Clear any existing children from this tag.
  410.         """
  411.         self._specials = {}
  412.         self.children = []
  413.         return self
  414.  
  415.     def __repr__(self):
  416.         rstr = ''
  417.         if self.attributes:
  418.             rstr += ', attributes=%r' % self.attributes
  419.         if self._specials:
  420.             rstr += ', specials=%r' % self._specials
  421.         if self.children:
  422.             rstr += ', children=%r' % self.children
  423.         return "Tag(%r%s)" % (self.tagName, rstr)
  424.  
  425.     def freeze(self):
  426.         """Freeze this tag so that making future calls to __call__ or __getitem__ on the
  427.         return value will result in clones of this tag.
  428.         """
  429.         def forever():
  430.             while True:
  431.                 yield self.clone()
  432.         return PatternTag(forever())
  433.  
  434.  
  435. class UnsetClass:
  436.     def __nonzero__(self):
  437.         return False
  438.     def __repr__(self):
  439.         return "Unset"
  440. Unset=UnsetClass()
  441.  
  442. def makeAccessors(special):
  443.     def getSpecial(self):
  444.         return self._specials.get(special, Unset)
  445.  
  446.     def setSpecial(self, data):
  447.         self._specials[special] = data
  448.  
  449.     return getSpecial, setSpecial
  450.  
  451. for name in Tag.specials:
  452.     setattr(Tag, name, property(*makeAccessors(name)))
  453. del name
  454.  
  455.  
  456.  
  457. def visit(root, visitor):
  458.     """
  459.     Invoke C{visitor} with each Tag in the stan DOM represented by C{root}.
  460.     """
  461.     if isinstance(root, list):
  462.         for t in root:
  463.             visit(t, visitor)
  464.     else:
  465.         visitor(root)
  466.         if isinstance(root, Tag):
  467.             for ch in root.children:
  468.                 visit(ch, visitor)
  469.  
  470.  
  471.  
  472. ### Pattern machinery
  473. class NodeNotFound(KeyError):
  474.     def __str__(self):
  475.         return "The %s named %r wasn't found in the template." % tuple(self.args[:2])
  476.  
  477. class TooManyNodes(Exception):
  478.     def __str__(self):
  479.         return "More than one %r with the name %r was found." % tuple(self.args[:2])
  480.  
  481. class PatternTag(object):
  482.     '''A pseudotag created by Tag.patternGenerator() which loops
  483.     through a sequence of matching patterns.'''
  484.  
  485.     def __init__(self, patterner):
  486.         self.pat = patterner.next()
  487.         self.patterner = patterner
  488.  
  489.     def next(self):
  490.         if self.pat:
  491.             p, self.pat = self.pat, None
  492.             return p
  493.         return self.patterner.next()
  494.  
  495.  
  496. def makeForwarder(name):
  497.     return lambda self, *args, **kw: getattr(self.next(), name)(*args, **kw)
  498.  
  499. for forward in ['__call__', '__getitem__', 'fillSlots']:
  500.     setattr(PatternTag, forward, makeForwarder(forward))
  501.  
  502. def _locatePatterns(tag, pattern, default, loop=True):
  503.     gen = specialMatches(tag, 'pattern', pattern)
  504.     produced = []
  505.  
  506.     for x in gen:
  507.         produced.append(x)
  508.         cloned = x.clone(deep=False, clearPattern=True)
  509.         yield cloned
  510.  
  511.     gen=None
  512.     if produced:
  513.         if not loop:
  514.             return
  515.         while True:
  516.             for x in produced:
  517.                 cloned = x.clone(deep=False, clearPattern=True)
  518.                 yield cloned
  519.  
  520.     if default is None:
  521.         raise NodeNotFound, ("pattern", pattern)
  522.     if hasattr(default, 'clone'):
  523.         while True:  yield default.clone(deep=False)
  524.     else:
  525.         while True:  yield default
  526. Tag._locatePatterns = staticmethod(_locatePatterns)
  527.  
  528.  
  529. def _locateOne(name, locator, descr):
  530.     found = False
  531.     for node in locator(name):
  532.         if found:
  533.             raise TooManyNodes(descr, name)
  534.         found = node
  535.     if not found:
  536.         raise NodeNotFound(descr, name)
  537.     return found
  538.  
  539.  
  540. def specials(tag, special):
  541.     """Generate tags with special attributes regardless of attribute value.
  542.     """
  543.     for childOrContext in getattr(tag, 'children', []):
  544.         child = getattr(childOrContext, 'tag', childOrContext)
  545.  
  546.         if getattr(child, special, Unset) is not Unset:
  547.             yield child
  548.         else:
  549.             for match in specials(child, special):
  550.                 yield match
  551.  
  552.  
  553. def specialMatches(tag, special, pattern):
  554.     """
  555.     Generate special attribute matches starting with the given tag; if a tag
  556.     has special, do not look any deeper below that tag, whether it matches
  557.     pattern or not. Returns an iterable.
  558.     """
  559.     for childOrContext in getattr(tag, 'children', []):
  560.         child = getattr(childOrContext, 'tag', childOrContext)
  561.  
  562.         data = getattr(child, special, Unset)
  563.         if data == pattern:
  564.             yield child
  565.         elif data is Unset:
  566.             for match in specialMatches(child, special, pattern):
  567.                 yield match
  568.  
  569. ## End pattern machinery
  570.  
  571.  
  572. class CommentProto(Proto):
  573.     __slots__ = []
  574.     def __call__(self, **kw):
  575.         return Comment(self)(**kw)
  576.  
  577.     def __getitem__(self, children):
  578.         return Comment(self)[children]
  579.  
  580.  
  581. class Comment(Tag):
  582.     def __call__(self, **kw):
  583.         raise NotImplementedError('comments are not callable')
  584.  
  585. invisible = Proto('')
  586.  
  587.  
  588. class Entity(object):
  589.     def __init__(self, name, num, description):
  590.         self.name = name
  591.         self.num = num
  592.         self.description = description
  593.  
  594.     def __repr__(self):
  595.         return "Entity(%r, %r, %r)" % (self.name, self.num, self.description)
  596.  
  597.  
  598. class inlineJS(object):
  599.     def __init__(self, children):
  600.         self.children = children
  601.  
  602.     def __repr__(self):
  603.         return "inlineJS(%s)" % (self.children, )
  604.